useImperativeHandle
🧑🏻💻 useImperativeHandle
useImperativeHandle
은 ref로 노출되는 핸들을 사용자가 직접 정의할 수 있게 해주는 React 훅이다.
( imperative : 명령적인, 피할 수 없는, 반드시 해야 하는, 필수의 )
✅ useImperativeHandle 문법
useImperativeHandle(ref, createHandle, dependencies?)
ref | forwardRef 렌더 함수에서 두 번째 인자로 받은 ref |
---|---|
createHandle | 인자가 없고 노출하려는 ref 핸들을 반환하는 함수 |
dependencies
는createHandle
코드 내에서 참조하는 모든 반응형 값을 나열한 목록이다. 리렌더링으로 인해 일부 의존성이 변경되거나 이 인수를 생략한 경우,createHandle
함수가 다시 실행되고 새로 생성된 핸들이 ref에 할당된다.useImperativeHandle
은undefined
를 반환한다. (return값 없음)
🧑🏻💻 알고 가기
✅ 주의 사항
- 컴포넌트의 최상위 레벨에서 호출 가능, 또한 커스텀 훅에서 호출 가능하다. (React 훅)
- prop으로 표현할 수 있는 것은 ref를 사용하지 않는다.
- 예를 들어,
Modal
컴포넌트에서{open, close}
와 같은 명령형 핸들로 노출하는 대신<Modal isOpen={isOpen} />
과 같은isOpen
prop을 권장한다. 이로써 코드가 더 간결하고 예측 가능하며, 컴포넌트의 독립성과 재사용성이 향상된다. - props로 표현할 수 없는 것에 대한 예로는 특정 노드로 스크롤하기, 노드에 초점 맞추기, 애니메이션 촉발하기, 텍스트 선택하기 등이 있다.
- 예를 들어,
🧑🏻💻 활용 및 생각할 거리
✅ useImperativeHandle와 forwardRef
forwardRef
와 함께 사용할 때부모 컴포넌트에서 자식 컴포넌트의 레퍼런스를 획득하고
useImperativeHandle
을 사용하여 레퍼런스에 노출할 메서드나 데이터를 정의한다. 이는 부모 컴포넌트에서 자식 컴포넌트를 직접 조작해야 하는 경우에 유용하다.forwardRef
없이 사용할 때컴포넌트 내부에서 특정 명령형 동작을 외부로 노출해야 하는 경우에
forwardRef
없이 가능하다.- 코드로 살펴보기
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
};
}, []);
return <input {...props} ref={inputRef} />;
});forwarRef만 사용하면 부모 컴포넌트는 전체
<input>
DOM 노드를 ref로 전달받게 된다.만약
<input>
DOM 노드 전체를 ref로 전달하는 대신 DOM 노드에 대한 사용자 지정 값만 부모에게 전달하고 싶다면, 위의 코드와 같이useImperativeHandle
를 사용하여 한 메서드만을 전달할 수 있다.import { useRef } from 'react';
import MyInput from './MyInput.js';
export default function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}이제 부모 컴포넌트가
MyInput
에 대한 ref를 가져오면focus
메서드를 호출할 수 있다. 그러나 기본<input>
DOM 노드의 전체 액세스 권한은 없다.
✅ useImperativeHandle를 왜 사용할까요?
객체 지향 프로그래밍에서는 캡슐화랑 은닉화가 중요하다. 비슷한 역할을 하는 속성(데이터)과 메소드(행동)들을 하나의 틀에 담는 것이다
- ex) 캡슐화 : 라면 끓이기 예시
- 가스레인지 : 가열의 역할
- 물 넣기 : 라면의 물을 맞추는 역할
- 재료 넣기 : 라면의 재료를 넣는 역할
이런 캡슐화는 기본적으로는 바람직하지만 button
이나 input
요소 같이 재 사용성이 높은 말단 요소(가장 끝에서 작동하는 요소)에서는 불편할 수 있다.
이런 경우 DOM 요소에 접근하기 위해 사용되었던 ref 같은 경우 캡슐화로 인한 불편한 예시가 될 수 있는데, 이럴 경우 우리는 forwardRef를 사용해서 props로 ref를 내려줄 수 있다. 그러나 이런 경우 ref를 통한 DOM 조작의 전 권을 다 주게 된다.
필요한 기능 외로 건드리면 문제가 발생할 수도 있다…😢
그렇기 때문에 이럴 경우 useImperativeHadle
을 사용한다. 원하는 DOM 조작 요소, 예를 들면 focus(), scroll() 같은 조작 요소들만 사용할 수 있게 허락할 수 있다!
✅ useImperativeHandle 사용 예시 코드
// App.js 부모 컴포넌트
import { useRef } from 'react';
import MyInput from './MyInput.js';
export default function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
// This won't work because the DOM node isn't exposed:
// 이 작업은 DOM 노드가 노출되지 않으므로 작동하지 않습니다.
// ref.current.style.opacity = 0.5;
}
return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}
// MyInput.js 자식 컴포넌트
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);
return <input {...props} ref={inputRef} />;
});
export default MyInput;
위에 코드와 같이 forwardRef로 전달 받은 ref 를 인수로 받으며 사용할 DOM 요소들만 정의해 준다. 부모 컴포넌트에서는 자식 컴포넌트에서 useImperativeHandle로 정의해 준 DOM 요소만 사용 가능하다.